import * as THREE from 'three';
// 从 three.js 示例中导入 GLTF 模型加载器，用于加载 .glb 或 .gltf 格式的 3D 模型
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
// 从 three.js 示例中导入轨道控制器，允许用户通过鼠标旋转、缩放、平移相机视角
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

export class ThreeManager {
  // 容器和配置项
  // 构造函数：在 new ThreeManager() 时被调用，用于初始化实例的基本属性。
  constructor(container, props) {
    this.container = container; //渲染 Three.js 画布的 DOM 元素（例如一个 div）
    this.props = props; //配置项
    this.scene = null; //场景
    this.camera = null; //相机
    this.renderer = null; //渲染器，画笔，将3d渲染到2d画布上
    this.controls = null; // 控制器：处理用户输入（鼠标、触摸）来控制相机
    this.model = null; // 加载的模型根对象：保存从文件中加载的 3D 模型

    this.requestId = null; // 动画帧 ID：用于 requestAnimationFrame，以便在需要时停止动画循环
    this.isAutoRotate = props.autoRotate; // 自动旋转开关：根据传入的 props 初始化，控制模型是否自动旋转
    this._disposed = false; // 标记是否已释放资源，防止重复调用 dispose
  }

  // 按顺序启动场景
  init() {
    this.initRenderer();
    this.initScene();
  }

  /**
   * 初始化渲染器：创建并配置 WebGL 渲染器。
   */
  initRenderer() {
    // 创建一个 WebGL 渲染器实例，antialias: true 开启抗锯齿，让模型边缘更平滑
    this.renderer = new THREE.WebGLRenderer({ antialias: true });
    this.renderer.setPixelRatio(window.devicePixelRatio); //设置渲染器的像素比
    // 设置渲染画布的尺寸，使其填满父容器
    this.renderer.setSize(
      this.container.clientWidth,
      this.container.clientHeight
    );
    // 设置场景的背景色，颜色值来自 props
    this.renderer.setClearColor(this.props.background);
    // 将渲染器创建的 <canvas> 元素添加到我们提供的 DOM 容器中
    this.container.appendChild(this.renderer.domElement);
  }

  /**
   * 初始化场景，相机，灯光和控制器
   */
  initScene() {
    // 创建场景实例
    this.scene = new THREE.Scene();
    // 计算容器宽高比，设置相机的视野
    const aspect = this.container.clientWidth / this.container.clientHeight;
    // 创建一个相机，模拟人眼睛的视觉效果，参数：（视野角度75度，宽高比，近裁切面0.1，远裁切面1000）
    this.camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
    this.camera.position.z = this.props.cameraZ;

    /**
     * 灯光
     */
    // 添加环境光，照亮场景中的所有物体
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // 提高环境光强度
    this.scene.add(ambientLight);
    // 添加平行光，产生阴影效果
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); // 提高平行光强度
    directionalLight.position.set(5, 5, 5);
    this.scene.add(directionalLight);

    // 添加额外的补光，让模型更亮
    const fillLight = new THREE.DirectionalLight(0xffffff, 0.8);
    fillLight.position.set(-5, 3, -5);
    this.scene.add(fillLight);

    // AxesHelper：辅助观察的坐标系
    // const axesHelper = new THREE.AxesHelper(150);
    // this.scene.add(axesHelper);

    // 创建轨道控制器实例，用户触摸控制
    this.controls = new OrbitControls(this.camera, this.renderer.domElement);
    this.controls.enableDamping = true; // 启用阻尼（惯性），使相机停止移动时有一个平滑的过渡效果
    this.controls.dampingFactor = 0.08; //阻尼系数
    this.controls.minDistance = 0.5;
    this.controls.maxDistance = 5;
    // 监听控制器的 'start' 事件（用户开始交互时触发）
    this.controls.addEventListener('start', () => {
      // 当用户开始操作时，关闭自动旋转
      this.isAutoRotate = false;
    });
  }

  /**
   * 加载 3D 模型。
   * @param {string} url - 模型文件的路径。
   * @param {function} onProgress - 加载过程中的回调函数，用于更新进度条。
   * @param {function} onComplete - 加载成功后的回调函数。
   * @param {function} onError - 加载失败后的回调函数。
   */
  loadModel(url, onComplete, onProgress, onError) {
    // 创建 GLTF 加载器实例
    const loader = new GLTFLoader();
    // 开始加载模型
    loader.load(
      url,
      (gltf) => {
        // 模型对象，成功回调
        this.model = gltf.scene;
        // 模型的包围盒子，计算中心点，自动化和标准化
        const box = new THREE.Box3().setFromObject(this.model);
        const center = new THREE.Vector3(); // 创建一个用来存储尺寸信息的向量 (x, y, z)
        const size = new THREE.Vector3();
        box.getCenter(center); // 获取边界盒的中心点
        box.getSize(size); // 获取边界盒的尺寸
        const maxDim = Math.max(size.x, size.y, size.z) || 1; // 找出最长边
        const scale = 2 / maxDim; // 计算一个缩放比例
        this.model.scale.setScalar(scale); // 应用这个缩放
        this.scene.add(this.model);

        const modelDepth = size.z * scale;
        this.model.position.set(0, -modelDepth / 2, 0);
        onComplete && onComplete();
        this.animate();
      },
      (xhr) => {
        // 加载中百分比获取
        const percentage = Math.round((xhr.loaded / xhr.total) * 100);
        // 调用外部传入的进度回调，更新 Vue 组件中的进度条
        onProgress && onProgress(percentage);
      },
      (err) => {
        // 加载失败
        console.error('GLTF 加载失败:', err);
        onError && onError(err);
      }
    );
  }

  /**
   * 动画循环函数，每一帧被调用，用于更新场景并重新渲染
   */
  animate() {
    this.requestId = requestAnimationFrame(() => this.animate());
    // 更新y轴角度
    if (this.isAutoRotate && this.scene) {
      this.model.rotation.y += this.props.rotationSpeed;
    }
    // 更新控制器（处理阻尼效果）
    if (this.controls) this.controls.update();
    // 使用渲染器将当前的场景和相机视角绘制到画布上
    this.renderer.render(this.scene, this.camera);
  }

  /**
   * 处理窗口大小改变事件：更新渲染器和相机的尺寸，以适应新的容器大小。
   */
  handleResize() {
    // 做好防御性检查，确保核心对象已存在
    if (!this.renderer || !this.camera || !this.container) return;
    // 获取容器的新宽高
    const w = this.container.clientWidth;
    const h = this.container.clientHeight;
    if (w && h) {
      // 更新渲染器画布的尺寸
      this.renderer.setSize(w, h);
      // 更新相机的宽高比
      this.camera.aspect = w / h;
      // 更新相机的投影矩阵，使新的宽高比生效
      this.camera.updateProjectionMatrix();
    }
  }

  /**
   * 销毁（清理）方法：在组件卸载时调用，释放所有占用的内存和资源，防止内存泄漏。
   */
  dispose() {
    // 防重复调用
    if (this._disposed) return;

    // 停止动画循环
    if (this.requestId) {
      cancelAnimationFrame(this.requestId);
      this.requestId = null;
    }

    // 销毁渲染器并移除其创建的 DOM 元素
    if (this.renderer) {
      this.renderer.dispose();
      if (
        this.renderer.domElement &&
        this.container.contains(this.renderer.domElement)
      ) {
        this.container.removeChild(this.renderer.domElement);
      }
      // 强制释放 WebGL 上下文
      if (this.renderer.forceContextLoss) {
        this.renderer.forceContextLoss();
      }
    }

    // 销毁控制器，移除其内部的事件监听器
    if (this.controls) {
      this.controls.dispose();
    }

    // 【重要】遍历场景中的所有对象，释放几何体和材质的显存
    if (this.scene) {
      this.scene.traverse((obj) => {
        if (obj.isMesh) {
          if (obj.geometry) obj.geometry.dispose();
          if (obj.material) {
            if (Array.isArray(obj.material)) {
              obj.material.forEach((m) => m.dispose && m.dispose());
            } else if (obj.material.dispose) {
              obj.material.dispose();
            }
          }
        }
      });
    }

    // 将所有引用置为 null，帮助垃圾回收器回收内存
    this.scene = null;
    this.camera = null;
    this.renderer = null;
    this.controls = null;
    this.model = null;
    this.container = null;
    this.props = null;

    // 标记已释放
    this._disposed = true;
  }
}
